Third-Party Payments Overview
July 16, 2024 In store we accept payments on behalf of three credit providers. All payments must be made in either Cash or Debit. The payments once made are then remitted to the provider that is paid. There are three types of credit for which we accept in-store payments: Daniel’s Credit, Synchrony, and Fortiva. The way the payments are resolved varies way but are reconciled in the end.
Making a Third-Party Payment In Store
A payment is made by adding a payment line to a transaction. This is done through the Transaction Command > Credit Account Payments… menu selection. A customer must be attached to a transaction and a sales associate assigned. This is not directly relevant to the transaction as sales credit is not important or measured for these sorts of transactions, but a requirement, nonetheless.
The customer’s account number must be specified. There is a check on the account number that is performed to make sure that it is a valid account. In the case of Daniel’s Credit the number will be autofilled if it is associated with their account. This number can be changed. There is no verification on payments to account numbers.
At time of payment, the customer must pay with either Debit or Cash. At transaction end the customer is provided a receipt like any regular transaction. It is possible to split payments.
Technical Processing of Third-Party Payments
At a high-level, when a payment is made a message is sent to the provider to let them know of the payment so that account can be adjusted correctly. How this message is sent (API realtime, API delayed, Batch file) depends on the circumstances of the transaction. As part of the accounting process at Daniel’s we settle this debt with the provider as we are acting as a passthrough. This is done through backend reporting.
Daniel’s Credit Payments
A Daniel’s Credit follows this process:
- An account is associated with a transaction. At time of account entry an API call to get account details is made to daniels.cssimpact.com. This information isn’t used for validation and approval like in other processes, but the contents of the call are available in
C:\Multidev\logs
. - When the payment is made three things happen:
- An API call is made to decrement the amount paid on the CSS account
- An entry in the local Firebird db to THIRD_PARTY_PAYMENTS is made which syncs to the master SV1020 database. This seems to happen within about a minute. This entry is made by a trigger on ORDERDETAIL (ORDERDETAIL_3PP_AL) that when the appropriate condition is matched creates the THIRD_PARTY_PAYMENTS entry.
- A payment retail line is added to the transaction. The product used is the one that matches PCLASS as defined in PRAJVALUEBYLEVEL.EXT_CRED_PCLASS
- Regardless of if the API call was successful we proceed and allow the transaction to be tendered. The line that is written to the database is different if the API call was not successful and the transactions will need to be reconciled with the system in a different way. NOTE: This only happens in in the case of a time out. If the address cannot be resolved or a local socket cannot be opened it will immediately error and not allow the transactions to be accepted.
- Unknown. A failure has not been able to be reliably created. The expected difference is that a successful API call should be marked with an ‘A’ as the PAYMENTSTATUS where a failure will be marked with an ‘L’. The ‘L’ should use the service to reconcile but currently do not. There is a usage that should show this information. The ‘L’ does appear to be set for these transactions as opposed to an ‘O’ which is the expected behavior.
Synchrony Payments
The Synchrony process requires a service running on the Multidev Application server to resolve transactions.
-
An account is associated with a transactions. This needs to be a Synchrony number. There is some sort of validation that it is a 16-digit number, perhaps with some other checks in the process. There is not a live call to validate the account.
-
An entry in the local Firebird db to OrderDetail is made which syncs to the SV1020 database. This seems to happen within about a minute. This entry is made by a trigger on ORDERDETAIL (ORDERDETAIL_3PP_AL) that when the appropriate condition is matched creates the THIRD_PARTY_PAYMENTS entry.
-
The entry that is created has a PAYMENTSTATUS of ‘L’. The service Multidev3rdParyCreditManagement runs once an hour (this seems to be regardless of settings). All transactions of the Synchrony type are picked up by this service and written out to C:\Multidev\EDI\synchrony.export_YYYYMMDD_HHMMSS.dat file. The output setting set in the service does not appear to influence where this writes out. When it writes out the file the ‘L’ is flipped to an ‘A’ indicating it was successfully processed.
-
The settlement file noted above is transmitted to Synchrony. This process is not implemented at the time of writing this documentation.
-
Testing and Validating Third-Party Payments
Multidev Third Party Payment Service
An additional service, the Multidev 3rd Party Payment Service is responsible for periodically scanning THIRD_PARTY_PAYMENTS for anything to be processed. This is determined by a PAYMENTSTATUS flag.
- **L: **set by the import from OrderDetail. It indicates a BATCH (Lot) type of operation, like the Synchrony export
- O: set by the import form OrderDetail. It indicates a one-to-one operation, where each entry will be processed against a specific processor, like the DHC API call
- E: set by the service: it indicates an error, with the reason set in the THIRD_PARTY_PAYMENTS.PROCESSING_ERROR** **field. To reprocess that line, the payment status should have to be set back to what is was initially (L/O) after the reason of the failure has been removed
- A: Set by the service. Indicates APPROVAL of the transaction, being batch or api processed. If available, a confirmation number will be stamped in the PROCESSOR_CONFID (ConfirmationID)
- R: set by the service. Indicates a REFUSAL to process this transaction because it has not been configured to process The service is controlled by an application named third-party payment service that allows modifying settings found at C:\Multidev\Superform\Multidev3rdPartyCreditManagementSvc.exe. This is very similar to MS Web Sync. Like MS Web Sync it must be run as Administrator.
Service Configuration
The following are stored in the Multidev3rdPartyCreditManagementSvc.ini** **file located at the same place as the service binary. It can be accessed from the service itself using the highlighted button
- Database Path: The database the service will connect.
- Minutes between cycles: Number of minutes before service will look for new data to process after previous work. This does not appear to change how frequently the Synchrony dat file is written out.
- Processing Operations
- CSV Extraction : this will cover (currently) 2 processes
- Extraction of data from THIRD_PARTY_PAYMENT_LOTS as CSV file and sending it via FTP
- Synchrony extraction following the settlement file format
- Direct API Calls
- Currently only performing Daniels House Credit on transactions that pos collected but was not able to send over to DHC. It is not known if this is functioning as expected.
- File Importation
- Proceeds Import of GLEntries and Deposits, if proper files are detected.
- CSV Extraction : this will cover (currently) 2 processes
- Mail ID : Script ID to be used when sending email for successful or erroneous processes
- Sent To Email : email address to which the above mentioned email will be sent
- Export folder path: Folder that will receive generated files, being CVS or Synchrony exports
These screens are not well known or described. As of 2024-07-16 there is an expectation for Multidev to provide additional information.
Which API to use for sending transactions to third party financial institutions
Currently, only DHC is allowed. Synchrony and Fortiva might be one day available, the logic is there and so is the config, even if not available
Database Configuration
There are some database configuration values used by the service in the table THIRD_PARTY_VENDORRULES. There are additional configuration attributes that are used as part of the settlement file creation. These exist in the SV1020 database in the table THIRD_PARTY_VENDORRULES in column VENDORCONFIG. These are the values set in the test environment as of 2024-07-16. FDR_SYS=8395 PRIN_BANK=0340 MERCHANT_ACCT=5348120340300318
There is a column FTPConfig .It seems that this config is used for sending the CSV exportation, most probably to Multidev, but I’m not sure of the initial intent
FTPSERVER=md-ftp.multidev.com FTPUSER=********* FTPPASSWORD=*********** FTPPORT=21 FTPFOLDER=/FTP/xxx PASSIVE=True PROTOCOL=FTPS
THIRD_PARTY_VENDORRULES.DO_BATCHCLOSE Values : Y / N Y: will create / use an entry on THIRD_PARTY_PAYMENT_LOTS. Typically used for Synchrony batches. It will also flag the newly created THIRD_PARTY_PAYMENTS** with a PAYMENTSTATUS = L N: will process required work without creating / managing the THIRD_PARTY_PAYMENT_LOTS. The newly created THIRD_PARTY_PAYMENT_LOTS will be flagged **with a PAYMENTSTATUS = O
THIRD_PARTY_VENDORRULES.DO_NEXTCLOSE Value: date Indicates when a new batch/lot needs to be created. The batch/lot creation process will update THIRD_PARTY_PAYMENT_LOTS entries from O (open) to P (processed) LOT_STATUS and will create a new O line. The NEXTCLOSE will be changed to indicate the next day.
Logs Most of the operations will write some traces in the Multidev3rdPartyCreditManagementSvc.log**. **This file will be renewed whenever it will reach 50 megs. Previous files will be saved with extension .last
Validating Transaction and Settlement File
Transactions can be run and verified that they are written to the THIRD_PARTY_PAYMENTS table. Once they are written the PAYMENTSTATUS flag can be monitored. The Synchrony account number 5046620120890256 can be used as a test account, at least in the test environment. Care should be taken when running production transactions. The settlement file follows a specific format. The settlement file is written out by Universe as of the writing of this documentation and moved over to Synchrony. This process creates the file identically so the same rails can be used with Synchrony without having to deeply involve them in the cutover. describes the expected format. Additionally, the following python script can be used to validate and confirm the appropriate fields are completed along with demonstrate what is expected in each field. This should be run as python saved_script_name.py path/to/the/file.dat.
import sys
if len(sys.argv) < 2:
print("Usage: python validate_synchrony_dat.py filename")
sys.exit(1)
# The second command-line argument is expected to be the filename
filename = sys.argv[1]
# Read the file content into a variable
try:
with open(filename, 'r') as file:
file_content = file.readlines()
# Now you can use file_content as needed
print("Two headers are expected in the file.")
print("==HEADER 1==")
print(f"A RECORD TYPE of 1: {file_content[0][0]}")
print(f"A FDR System Number: {file_content[0][1:5]}")
print(f"A Principal Bank Number: {file_content[0][5:9]}")
print(f"A Date of Tape (ddmmyy?): {file_content[0][9:15]}")
print(f"A Reject Tape Indicator should be N: {file_content[0][15]}")
print(f"A format code should be 1: {file_content[0][16]}")
print(f"A filler of three spaces: {file_content[0][17:20] == ' '}")
print(f"A reject discrepancy setting of 49: {file_content[0][20:22]}")
print(f"A filler of five spaces: {file_content[0][22:27] == ' '}")
print(f"A merchant page break flag of Y: {file_content[0][27]}")
print(f"A filler of 52 spaces to the end of the line: {file_content[0][28:80] == ' ' * 52}")
print("==HEADER 2==")
print(f"A RECORD TYPE of 2: {file_content[1][0]}")
print(f"A Merchant Number: {file_content[1][1:17]}")
print(f"A filler of three spaces: {file_content[1][17:20] == ' '}")
print(f"A reference number indicator of 1: {file_content[1][20]}")
print(f"A float indicator of 00 (two zeros): {file_content[1][21:23]}")
print(f"A filler of 13 spaces: {file_content[1][23:36] == ' ' * 13}")
print(f"A compiliance indicator of C: {file_content[1][36]}")
print(f"A filler of 19 spaces: {file_content[1][37:56] == ' ' * 19}")
print(f"A WST EBH DESC TERMS SW indicator of 1: {file_content[1][56]}")
print(f"A filler of 2 spaces: {file_content[1][57:59] == ' '}")
print(f"A WST EBH DEPT CODE SW indicator of Y: {file_content[1][59]}")
print(f"A filler of 20 spaces: {file_content[1][60:80] == ' ' * 20}")
print("==TRANSACTION RECORDS==")
# Loop through the transaction records, exclude last two and first two lines
transactions = file_content[2:-2]
transaction_amounts = []
for i, transaction in enumerate(transactions):
print(f"==TRANSACTION RECORD {i + 1}==")
print(f"A RECORD TYPE of 5: {transaction[0]}")
print(f"A cardholder account number of 16 digits: {transaction[1:17]}")
print(f"A transaction date of 6 digits: {transaction[17:23]}")
print(f"A transaction amount of 7 digits, 0 left padded: {transaction[23:30]} or {'$' + transaction[23:28].lstrip('0') + '.' + transaction[28:30]}")
transaction_amounts.append(transaction[23:30])
print(f"A transaction code of P: {transaction[30]}")
print(f"In store payment tender type (seems wrong): {transaction[31:33]}")
print(f"Merchant transaction identifier: {transaction[33:39]}")
print(f"Ticket terms (should be '0000'): {transaction[39:43]}")
print(f"A filler of 37 spaces (department=16, filler=20, addenda-record=1): {transaction[43:80] == ' ' * 37}")
print("==TRAILER 1==")
print(f"A RECORD TYPE of 8: {file_content[-2][0]}")
print(f"A Merchant Number: {file_content[-2][1:17]}")
print(f"Number of details records (should be {len(transactions)}): {file_content[-2][17:24]}")
total_amount = sum([int(amount) for amount in transaction_amounts])
total_amount_as_string = formatted_amount = "${:,.2f}".format(total_amount)
print(f"Net total of all transaction amounts (should be {total_amount}): {file_content[-2][24:33]}")
print(f"A filler of 47 spaces: {file_content[-2][33:80] == ' ' * 47}")
print("==TRAILER 2==")
print(f"A RECORD TYPE of 9: {file_content[-1][0]}")
print(f"FDR System Number: {file_content[-1][1:5]}")
print(f"Principal Bank Number: {file_content[-1][5:9]}")
print(f"Number of details records (should be the same as the previous trailer): {file_content[-1][9:18]}")
print(f"Net dollar amount: {file_content[-1][18:29]}")
print(f"A filler of 51 spaces: {file_content[-1][29:80] == ' ' * 51}")
except FileNotFoundError:
print(f"The file {filename} was not found.")